Fixed required-features not working with dependencies' features for issue #3727.
authorJames Bendig <jbendig@starbytesoftware.com>
Mon, 20 Feb 2017 19:31:03 +0000 (13:31 -0600)
committerJames Bendig <jbendig@starbytesoftware.com>
Mon, 20 Feb 2017 19:31:03 +0000 (13:31 -0600)
src/cargo/ops/cargo_compile.rs
tests/required-features.rs

index ee882b7b6f4d1ffdc8b830164c84c8b28445e9e2..38c0efa00a0a9f8979b55b2ed336011c368812d2 100644 (file)
@@ -28,7 +28,8 @@ use std::path::PathBuf;
 use std::sync::Arc;
 
 use core::{Source, Package, Target};
-use core::{Profile, TargetKind, Profiles, Workspace, PackageIdSpec};
+use core::{Profile, TargetKind, Profiles, Workspace, PackageId, PackageIdSpec};
+use core::resolver::Resolve;
 use ops::{self, BuildOutput, Executor, DefaultExecutor};
 use util::config::Config;
 use util::{CargoResult, profile};
@@ -188,8 +189,8 @@ pub fn compile_ws<'a>(ws: &Workspace<'a>,
         }
     } else {
         let root_package = ws.current()?;
-        let all_features = resolve_with_overrides.features(root_package.package_id());
-        generate_targets(root_package, profiles, mode, filter, all_features, release)?;
+        let all_features = resolve_all_features(&resolve_with_overrides, root_package.package_id());
+        generate_targets(root_package, profiles, mode, filter, &all_features, release)?;
         pkgids.push(root_package.package_id());
     };
 
@@ -206,9 +207,9 @@ pub fn compile_ws<'a>(ws: &Workspace<'a>,
             panic!("`rustc` and `rustdoc` should not accept multiple `-p` flags")
         }
         (Some(args), _) => {
-            let all_features = resolve_with_overrides.features(to_builds[0].package_id());
+            let all_features = resolve_all_features(&resolve_with_overrides, to_builds[0].package_id());
             let targets = generate_targets(to_builds[0], profiles,
-                                           mode, filter, all_features, release)?;
+                                           mode, filter, &all_features, release)?;
             if targets.len() == 1 {
                 let (target, profile) = targets[0];
                 let mut profile = profile.clone();
@@ -221,9 +222,9 @@ pub fn compile_ws<'a>(ws: &Workspace<'a>,
             }
         }
         (None, Some(args)) => {
-            let all_features = resolve_with_overrides.features(to_builds[0].package_id());
+            let all_features = resolve_all_features(&resolve_with_overrides, to_builds[0].package_id());
             let targets = generate_targets(to_builds[0], profiles,
-                                           mode, filter, all_features, release)?;
+                                           mode, filter, &all_features, release)?;
             if targets.len() == 1 {
                 let (target, profile) = targets[0];
                 let mut profile = profile.clone();
@@ -237,9 +238,9 @@ pub fn compile_ws<'a>(ws: &Workspace<'a>,
         }
         (None, None) => {
             for &to_build in to_builds.iter() {
-                let all_features = resolve_with_overrides.features(to_build.package_id());
+                let all_features = resolve_all_features(&resolve_with_overrides, to_build.package_id());
                 let targets = generate_targets(to_build, profiles, mode,
-                                               filter, all_features, release)?;
+                                               filter, &all_features, release)?;
                 package_targets.push((to_build, targets));
             }
         }
@@ -273,7 +274,29 @@ pub fn compile_ws<'a>(ws: &Workspace<'a>,
 
     ret.to_doc_test = to_builds.iter().map(|&p| p.clone()).collect();
 
-    Ok(ret)
+    return Ok(ret);
+
+    fn resolve_all_features(resolve_with_overrides: &Resolve,
+                            package_id: &PackageId)
+                            -> HashSet<String> {
+        let mut features = match resolve_with_overrides.features(package_id) {
+            Some(all_features) => all_features.clone(),
+            None => HashSet::new(),
+        };
+
+        // Include features enabled for use by dependencies so targets can also use them with the
+        // required-features field when deciding whether to be built or skipped.
+        let deps = resolve_with_overrides.deps(package_id);
+        for dep in deps {
+            if let Some(dep_features) = resolve_with_overrides.features(dep) {
+                for feature in dep_features {
+                    features.insert(dep.name().to_string() + "/" + feature);
+                }
+            }
+        }
+
+        features
+    }
 }
 
 impl<'a> CompileFilter<'a> {
@@ -318,7 +341,7 @@ fn generate_targets<'a>(pkg: &'a Package,
                         profiles: &'a Profiles,
                         mode: CompileMode,
                         filter: &CompileFilter,
-                        features: Option<&HashSet<String>>,
+                        features: &HashSet<String>,
                         release: bool)
                         -> CargoResult<Vec<(&'a Target, &'a Profile)>> {
     let build = if release {&profiles.release} else {&profiles.dev};
@@ -430,8 +453,6 @@ fn generate_targets<'a>(pkg: &'a Package,
     };
 
     //Collect the targets that are libraries or have all required features available.
-    let no_features = HashSet::new();
-    let features = features.unwrap_or(&no_features);
     let mut compatible_targets = Vec::with_capacity(targets.len());
     for (target, profile) in targets.drain(0..) {
         if target.is_lib() || match target.required_features() {
index c230ddb1932b0365f6418c55201a328f137b517a..e65ea4c16548597002aa157a8ac6d0a83bb70c7f 100644 (file)
@@ -745,3 +745,237 @@ fn install_multiple_required_features() {
     assert_that(cargo_home(), not(has_installed_exe("foo_1")));
     assert_that(cargo_home(), not(has_installed_exe("foo_2")));
 }
+
+#[test]
+fn dep_feature_in_toml() {
+    let p = project("foo")
+        .file("Cargo.toml", r#"
+            [project]
+            name = "foo"
+            version = "0.0.1"
+            authors = []
+
+            [dependencies]
+            bar = { path = "bar", features = ["a"] }
+
+            [[bin]]
+            name = "foo"
+            required-features = ["bar/a"]
+
+            [[example]]
+            name = "foo"
+            required-features = ["bar/a"]
+
+            [[test]]
+            name = "foo"
+            required-features = ["bar/a"]
+
+            [[bench]]
+            name = "foo"
+            required-features = ["bar/a"]
+        "#)
+        .file("src/main.rs", "fn main() {}")
+        .file("examples/foo.rs", "fn main() {}")
+        .file("tests/foo.rs", "#[test]\nfn test() {}")
+        .file("benches/foo.rs", r#"
+            #![feature(test)]
+            extern crate test;
+
+            #[bench]
+            fn bench(_: &mut test::Bencher) {
+            }"#)
+        .file("bar/Cargo.toml", r#"
+            [project]
+            name = "bar"
+            version = "0.0.1"
+            authors = []
+
+            [features]
+            a = []
+        "#)
+        .file("bar/src/lib.rs", "");
+    p.build();
+
+    assert_that(p.cargo("build"),
+                execs().with_status(0));
+
+    // bin
+    assert_that(p.cargo("build").arg("--bin=foo"),
+                execs().with_status(0));
+    assert_that(&p.bin("foo"), existing_file());
+
+    // example
+    assert_that(p.cargo("build").arg("--example=foo"),
+                execs().with_status(0));
+    assert_that(&p.bin("examples/foo"), existing_file());
+
+    // test
+    assert_that(p.cargo("test").arg("--test=foo"),
+                execs().with_status(0).with_stderr(format!("\
+[COMPILING] foo v0.0.1 ({})
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] target[/]debug[/]deps[/]foo-[..][EXE]", p.url()))
+                .with_stdout("
+running 1 test
+test test ... ok
+
+test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
+
+"));
+
+    // bench
+    if is_nightly() {
+        assert_that(p.cargo("bench").arg("--bench=foo"),
+                    execs().with_status(0).with_stderr(format!("\
+[COMPILING] bar v0.0.1 ({0}/bar)
+[COMPILING] foo v0.0.1 ({0})
+[FINISHED] release [optimized] target(s) in [..]
+[RUNNING] target[/]release[/]deps[/]foo-[..][EXE]", p.url()))
+                    .with_stdout("
+running 1 test
+test bench ... bench: [..] 0 ns/iter (+/- 0)
+
+test result: ok. 0 passed; 0 failed; 0 ignored; 1 measured
+
+"));
+    }
+
+    // install
+    assert_that(p.cargo("install"),
+                execs().with_status(0));
+    assert_that(cargo_home(), has_installed_exe("foo"));
+    assert_that(p.cargo("uninstall").arg("foo"),
+                execs().with_status(0));
+}
+
+#[test]
+fn dep_feature_in_cmd_line() {
+    let p = project("foo")
+        .file("Cargo.toml", r#"
+            [project]
+            name = "foo"
+            version = "0.0.1"
+            authors = []
+
+            [dependencies]
+            bar = { path = "bar" }
+
+            [[bin]]
+            name = "foo"
+            required-features = ["bar/a"]
+
+            [[example]]
+            name = "foo"
+            required-features = ["bar/a"]
+
+            [[test]]
+            name = "foo"
+            required-features = ["bar/a"]
+
+            [[bench]]
+            name = "foo"
+            required-features = ["bar/a"]
+        "#)
+        .file("src/main.rs", "fn main() {}")
+        .file("examples/foo.rs", "fn main() {}")
+        .file("tests/foo.rs", "#[test]\nfn test() {}")
+        .file("benches/foo.rs", r#"
+            #![feature(test)]
+            extern crate test;
+
+            #[bench]
+            fn bench(_: &mut test::Bencher) {
+            }"#)
+        .file("bar/Cargo.toml", r#"
+            [project]
+            name = "bar"
+            version = "0.0.1"
+            authors = []
+
+            [features]
+            a = []
+        "#)
+        .file("bar/src/lib.rs", "");
+    p.build();
+
+    assert_that(p.cargo("build"),
+                execs().with_status(0));
+
+    // bin
+    assert_that(p.cargo("build").arg("--bin=foo"),
+                execs().with_status(101).with_stderr("\
+error: target `foo` requires the features: `bar/a`
+Consider enabling them by passing e.g. `--features=\"bar/a\"`
+"));
+
+    assert_that(p.cargo("build").arg("--bin=foo").arg("--features").arg("bar/a"),
+                execs().with_status(0));
+    assert_that(&p.bin("foo"), existing_file());
+
+    // example
+    assert_that(p.cargo("build").arg("--example=foo"),
+                execs().with_status(101).with_stderr("\
+error: target `foo` requires the features: `bar/a`
+Consider enabling them by passing e.g. `--features=\"bar/a\"`
+"));
+
+    assert_that(p.cargo("build").arg("--example=foo").arg("--features").arg("bar/a"),
+                execs().with_status(0));
+    assert_that(&p.bin("examples/foo"), existing_file());
+
+    // test
+    assert_that(p.cargo("test"),
+                execs().with_status(0).with_stderr(format!("\
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]"))
+                .with_stdout(""));
+
+    assert_that(p.cargo("test").arg("--test=foo").arg("--features").arg("bar/a"),
+                execs().with_status(0).with_stderr(format!("\
+[COMPILING] foo v0.0.1 ({})
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] target[/]debug[/]deps[/]foo-[..][EXE]", p.url()))
+                .with_stdout("
+running 1 test
+test test ... ok
+
+test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
+
+"));
+
+    // bench
+    if is_nightly() {
+        assert_that(p.cargo("bench"),
+                    execs().with_status(0).with_stderr(format!("\
+[FINISHED] release [optimized] target(s) in [..]"))
+                    .with_stdout(""));
+
+        assert_that(p.cargo("bench").arg("--bench=foo").arg("--features").arg("bar/a"),
+                    execs().with_status(0).with_stderr(format!("\
+[COMPILING] bar v0.0.1 ({0}/bar)
+[COMPILING] foo v0.0.1 ({0})
+[FINISHED] release [optimized] target(s) in [..]
+[RUNNING] target[/]release[/]deps[/]foo-[..][EXE]", p.url()))
+                    .with_stdout("
+running 1 test
+test bench ... bench: [..] 0 ns/iter (+/- 0)
+
+test result: ok. 0 passed; 0 failed; 0 ignored; 1 measured
+
+"));
+    }
+
+    // install
+    assert_that(p.cargo("install"),
+                execs().with_status(101).with_stderr(format!("\
+[INSTALLING] foo v0.0.1 ([..])
+[FINISHED] release [optimized] target(s) in [..]
+[ERROR] no binaries are available for install using the selected features
+")));
+    assert_that(cargo_home(), not(has_installed_exe("foo")));
+
+    assert_that(p.cargo("install").arg("--features").arg("bar/a"),
+                execs().with_status(0));
+    assert_that(cargo_home(), has_installed_exe("foo"));
+    assert_that(p.cargo("uninstall").arg("foo"),
+                execs().with_status(0));
+}